/**
 * SashaNlgSystem :)
 */


// This declares that this file belongs to a Java package.
package ar.cordoba.unc.famaf.pln.sashaNlgSystem;


// This includes all the functions/methods we are
// going to use.
import give.comm.ClientMetadata;
import give.comm.internal.InternalMessage;
import give.comm.internal.InternalMessageType;

import give.formula.*;
import give.nlgserver.*;
import give.nlgserver.discretize.Discretizer;	// To know how the world is divided in regions.
import give.questionnaire.QuestionnaireData;	// To retrieve the information of the initial questionnaire.
import give.world.*;

import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.util.*;

import com.google.common.collect.Sets;


/**
 * 
 * The main class of the system.
 * 
 * It should implement the following seven mandatories functions/methods:
 * 
 * 		- SashaNlgSystem: 			The class creator. Executed only once, when the program is executed.
 * 		- connectionEstablished: 	Executed whenever a game starts.
 * 		- connectionDisconnected: 	Executed whenever a game ends.
 * 		- 
 */
@SuppressWarnings("serial")
public class SashaNlgSystem extends NlgSystem

{

	/***********************************************************/
	/**						 								  **/
	/**			Variable's Declarations and Definitions	  	  **/
	/**						 								  **/
	/***********************************************************/



	/***********************************************************
	 * 
	 * General Variables ======================================>
	 *  
	 **********************************************************/

	/* Used for sending "close_door" and "open_door" messages to the give client */ 
	NlgServerInstance server_instance;
	
	/* A variable that will hold information of the current world state */
	World world;

	/* It will contain the name of the room where the subject is. Initially MUST be "" */
	String current_subject_room;
	
	/* This variable holds an object which is used for sending text messages to the subject */
	DelayedSendingThread sender;

	/* It will hold the set of buttons that the subject has pressed in the current room */
	Set<String> pressed_buttons = new HashSet<String>();
	
	/* It will hold the set of buttons that the system has referred by a referring expression 
	 * and that the subject has tried to press */
	Set<String> referred_buttons = new HashSet<String>();
	
	/* The list of objects that the subject can see */
	List<String> visibleObjects = new ArrayList<String>();
	
	/* It will hold the name of the last button that was referenced by the system. Used in Training and Exercise phases */
	String current_button;
	boolean waiting_for_new_ref = false;
	
	/* It will save the message of the last object that was referenced by the system. Used in Training and Exercise phases */
	String current_reference_message;
	
	/* True when the system should start the countdown and keep showing "current_button" */
	boolean do_countdown = false;
	
	/* True when the system has to wait for the subject to get inside a new room. Initially MUST be true */
	boolean waiting_for_new_room = false;
	
	/* Will be true when the system has to wait for sending the first reference msg until the welcome messages for the room are showed completely */
	boolean mustSendReference = false;
	
	/* Change the value of the next variable to set the timeout */
	int timeout_for_training = 30; 			// 30 seconds
	int timeout_for_exercising = 60;		// 60 seconds
	
	/* This variable will holds the number of times the handleStatusInformation() method has been invoked */
	int timeout_frames;

	/* This variable will hold the number of correct button presses */
	public static int correct_press = 0;
	
	/* 
	 * Specifies which kind of messages should the system 
	 * use in the Exercise phase for the initial reference.
	 * If true then the over specified messages will be used
	 * otherwise it will use the minimal type of messages. 
	 * */
	boolean use_overspecified = false;
	
	/*
	 * Specifies which conditions we are gonna use to change from minimal to overspecified
	 * types of expressions while giving a reference in the EXERCISE phase.
	 */
	boolean use_distance_condition = true; 			// Change from min to over when the distance between the subject and the referent increases.
	double distance_margin = 0.5;					// Specifies the distance the subject could move away from the referent without give a oversp ref in an interval of 2.5 seconds.
	//int distance_timerate = 5;
	double current_distance = 0.;					// DO NOT CHANGE THIS. Temporal variables to save the old and new distance.
	double closest_distance = 4000.;				// DO NOT CHANGE THIS.
	boolean disable_distance_condition = false;		// DO NOT CHANGE THIS.
	
	boolean use_go_to_middle_condition = true;		// The subject must move to the center of the room to get an expression for an object.
	double distance_from_middle = 1.;				// We will consider that the subject is on the middle when he's closer of 1 from the real center.
	String message_to_go_to_middle = "Ahora anda al medio de la habitacion, por favor"; // Message to show after the subj presses a button.
	boolean disable_use_to_go_middle_condition = false; 	// DO NOT CHANGE THIS.

	boolean use_too_close_dist_condition = false; 	// Chenge from min to over when the subject gets very close of a distractor.
	double too_close_distractor_distance = 0.2; // Specifies what is to be very "close" of a distractor object.
	

	/*
	 * Types of rooms and experiments.
	 * Add a new room type implies several code changes.
	 */
	enum RoomType {
		TUTORIAL_ROOM,	// For this kind of room, the behavior is not configurable.
		PRIMMING_ROOM,	// In this kind of rooms, the subject must press a set of buttons, then read a message.
		TRAINING_ROOM,	// In this experiment, the subject must read a message, then press a button.
		EXERCISE_ROOM	// Like TRAINING, but allows two types of message specifications and gives feedback.
	};
	
	/*
	 * Room names as figures in the ORRE-world_x.x.x.giveworld file.
	 * If you want to change a room name, do it here.
	 * If you need to add a new room, define a new variable and add its name.
	 */
	String tutorial_room_name 	= "tutorial-room";	// The tutorial room is a special room and it is not treated like other rooms.
	String object_room_name 	= "object-room";
	String color_room_name 		= "color-room";
	String relation_room_name 	= "relation-room";
	String test_room_name 		= "test-room1";
	String exercise_room_name 	= "exercise-room";
	String corridor_room_name	= "corridor";

	/*
	 * A dictionary/map that will hold all the room names 
	 * that were defined for the experiment as they are specified 
	 * in the ORRE-world_x.x.x.giveworld file.
	 * It relates room names with types of rooms.
	 * If you have added new rooms, you must add a new entry here.
	 */
	Map<String, RoomType> map_of_room_types = new HashMap<String, RoomType>() {
		{
			put(tutorial_room_name, RoomType.TUTORIAL_ROOM);
			put(object_room_name, 	RoomType.PRIMMING_ROOM);
			put(color_room_name, 	RoomType.PRIMMING_ROOM);
			put(relation_room_name, RoomType.PRIMMING_ROOM);
			put(test_room_name, 	RoomType.TRAINING_ROOM);
			put(exercise_room_name, RoomType.EXERCISE_ROOM);
		}
	};


	/*
	 * The next list specifies the rooms that the subject
	 * must complete in order to finish the experiment.
	 * Note that the sequence: test_room_name, exercise_room_name is twice
	 * because the subject must complete both rooms two times.
	 */
	List<String> list_of_rooms_order = new ArrayList<String>(Arrays.asList
		(
			tutorial_room_name,
			object_room_name,
			color_room_name,
			relation_room_name,
			test_room_name,
			exercise_room_name,
			corridor_room_name,
			test_room_name
		)
	);


	/*
	 * A dictionary/map contains the messages that
	 * the system should send when the subject enters to a
	 * specific room.
	 * Add a new entry if you want to add a new message.
	 */ 
	Map<String, String> map_of_welcome_msgs = new HashMap<String, String>() {
		{
			put(tutorial_room_name, "Bienvenidos al desafio ORRE!|" +
									"Antes de empezar con el experimento,|" +
									"vamos a explicarle como usar los controles del juego.|" +
									"Puedes caminar adelante presionando la tecla CURSOR ARRIBA|"+
									"y atras presionando la tecla CURSOR ABAJO|" +
									"Para girar, utilice las teclas IZQUIERDA y DERECHA|" +
									"Pruebelo!|"+
									" |" +
									" |" +
									"Para presionar un boton,|" + 
									"tiene que estar lo mas cerca posible del pared en cual esta|" + 
									"y utilizar el cursor del raton para presionarlo.|" +	
									"Solamente los botones negros pueden ser presionados.|" +
									"El boton a la derecha de un objeto corresponde a el.|" +
									"Si te pones demasiado cerca de un objeto, no podrias presionar botones.|"+
									"Pruebe a presionar el boton cerca de la silla.|"+
									" |" +
									" |" +
									"En el experimento, aprenderas algunas palabras en ruso,|" +
									"y despues tendrias que reconocer a estos objetos en el mundo virtual.|" +
									"Para iniciar el experimento, presiona el boton junto al boton rojo.");
			
			put(object_room_name, 	"Bienvenidos a la Sala de Objetos!|" +
									"En esta sala vas a aprender los nombres de los objetos en ruso.|" +
									"Presione el boton al lado de cada objeto para ver la palabra en ruso.|"+
									"Trata de recordarte de las palabras que veas!!");

			put(color_room_name,	"Bienvenidos a la Sala de Colores!|" + 
									"En esta sala vas a aprender los nombres de los colores en ruso.|" +
									"Presione el boton negro para ver la color de arriba en ruso.|");

			put(relation_room_name,	"Bienvenidos a la Sala de los Lados!|" + 
									"En esta sala vas a aprender a decir \" a la derecha de\"|" + 
									"y \"a la izquierda de\" en ruso");

			put(test_room_name,		"Bienvenidos a la Fase de Pruebas!|" +
									"En esta sala vamos a evaluar que tan bien recuerdas el vocabulario que has aprendido.|" +
									"Cuando veas la palabra en ruso, encontra ese objeto en la sala.");
			
			put(exercise_room_name,	"Bienvenidos a la Fase de Ejercicios!|" +
						"En esta sala vas a entrenarte con objetos mas complejos.|" +
						"Cuando veas la expresion en ruso, encontra ese objeto en la sala.|" +
						"Vamos a decirte si has encontrado el buen objeto o no...|" +
						"...pero puedes intentar una sola vez.|");
		}
	};
	int welcome_msgs_time = 3500;



	/*
	 * Set the list of buttons that the subject has to manipulate 
	 * in order to finish each room.
	 */
	HashMap<String, List<String>> map_of_buttons = new HashMap<String, List<String>>() {
		{
			// Tutorial phase.
			put(tutorial_room_name, new ArrayList<String> (
					Arrays.asList("bt22", "bt65")
			));
			
			// Primming phase.
			put(object_room_name, new ArrayList<String> (
				Arrays.asList("bt7","bt8", "bt10")
			));
			put(color_room_name, new ArrayList<String> (
				Arrays.asList("bt52","bt53", "bt54", "bt55")
			));
			put(relation_room_name, new ArrayList<String> (
				Arrays.asList("bt56","bt57")
			));

			// Testing phase.
			put(test_room_name, new ArrayList<String> (
				Arrays.asList("bt45", "bt59", "bt60", "bt61", "bt47", "bt58", "bt24")	
			));

			// Exercise phase.
			put(exercise_room_name, new ArrayList<String> (
				Arrays.asList("bt25", "bt4", "bt2", "bt3", "bt29", "bt31", "bt27", "bt44", "bt9", "bt15", "bt16", "bt21")
			));
		}
	};
	
	
	
	/*
	 * The next map saves which doors the system must open and then close
	 * when the subject has finished with a room.
	 */
	HashMap<String, List<String>> map_of_doors = new HashMap<String, List<String>>() {
		{
			// Tutorial phase.
			put(tutorial_room_name, new ArrayList<String> (
					Arrays.asList("dt1")
			));
			
			// Primming phase.
			put(object_room_name, new ArrayList<String> (
				Arrays.asList("dt2")
			));
			put(color_room_name, new ArrayList<String> (
				Arrays.asList("dt3")
			));
			put(relation_room_name, new ArrayList<String> (
				Arrays.asList("dt4")
			));

			// Testing phase.
			put(test_room_name, new ArrayList<String> (
				Arrays.asList("dt5")
			));

			// Exercise phase.
			put(exercise_room_name, new ArrayList<String> (
				Arrays.asList("dt6")
			));
			
			// Corridor.
			put(corridor_room_name, new ArrayList<String> (
				Arrays.asList("dt8")
			));
		}
	};


	/*
	 * Holds the exit messages for each room, i.e., the messages
	 * that the system should send when the subject leaves a room.
	 */
	Map<String, String> map_of_exit_msgs = new HashMap<String, String>() {
		{
			// Tutorial phase.
			put(tutorial_room_name, "Muy bien!| Ahora puedes empezar el experimento ORRE!|" +
				     				"Pasa por la puerta y hace lo mejor que puedes!");
			
			// Other phases.
			put(object_room_name,	"Ahora, por favor pase a la sala siguiente");
			put(color_room_name, 	"Ahora, por favor pase a la sala siguiente");
			put(relation_room_name, "Ahora, por favor pase a la sala siguiente");
			put(test_room_name,		"Ahora, por favor pase a la sala siguiente");
			put(exercise_room_name,	"Ahora, por favor pase a la sala siguiente");
			put(corridor_room_name,	"Continua a la ultima sala");
		}
	};
	int exit_msgs_time = 3000;	// The time that the message should be shown on screen.


	/*
	 * Messages for give feedback to the subject in the testing and
	 * exercise rooms.
	 */
	String positive_feedback = "Excelente!";
	int positive_feedback_time = 2000;	// The time that the message should be shown on screen.
	
	String negative_feedback = "No, ese no era objeto correcto, Pruebe con la siguiente expresion";
	int negative_feedback_time = 3000;	// 3 seconds
	
	String pressed_twice_msg = "Ya recibiste el nombre de este objeto, intenta con otro!";
	int pressed_twice_msg_time = 3000;	// 3 seconds
	
	
	/* Message given when the game ends */
	String end_msg = "Felicitaciones!| Has aprendido algo de ruso y ayudado a nuestro experimento!| Muchas gracias!";
	int end_msg_time = 3000;;
	
 

	/******************************************************************
	 * 
	 * Variables for the Tutorial Phase ===============================>
	 * 
	 *****************************************************************/



	/*
	 * A dictionary/map that contains the words that the system 
	 * should say when the subject presses a specific button in the tutorial 
	 * room.
	 */
	Map<String, String> map_of_tutorial_buttons_for_msgs = new HashMap<String, String>() {
		{
			put("bt22", "Silla");
		}
	};
	int tutorial_buttons_for_msgs_time = 5000;	// 5 seconds


	/*
	 * The next map associates the words 
	 * that the system should give with the buttons that the subject 
	 * must press for all the rooms in the tutorial phase. The 
	 * system will verbalize the messages in the same order they 
	 * are inserted in the map.
	 */
	Map<String, String> map_of_tutorial_msgs_for_buttons = new HashMap<String, String>() {
		{
			put("bt65", "Rojo");
		}
	};
	int tutorial_msgs_for_buttons_time = 5000;	// 5 seconds




	/******************************************************************
	 * 
	 * Variables for the Priming Phase ===============================>
	 * 
	 *****************************************************************/


	/*
	 * A dictionary/map that will contain the messages that the system 
	 * should say when the subject presses a specific button.
	 * Contains all the buttons of the primming-type rooms.
	 * For each new primming-type room you add, you will have to add 
	 * new entries here.
	 */
	Map<String, String> map_of_buttons_for_msgs = new HashMap<String, String>() {
		{
			// object-room's buttons messages
			put("bt7", "stul");
			put("bt8", "palka");
			put("bt10", "svet");

			// color-room's buttons messages
			put("bt52", "sinii");
			put("bt53", "ovoshii");
			put("bt54", "zoltii");
			put("bt55", "krasnii");

			// relation-room's buttons messages
			put("bt56", "SPRAVA OT = a la DERECHA de ");
			put("bt57", "SLEVA OT = a la IZQUIERDA de ");
		}
	};
	int buttons_for_msgs_time = 5000;	// 5 seconds



	
	/****************************************************************************
	 * 
	 * Variables for the Testing Phase =========================================>
	 * 
	 ***************************************************************************/


	/*
	 * The next map associates the messages 
	 * that the system should give with the buttons that the subject 
	 * must press for all the rooms of the testing phase. The 
	 * system will verbalize the messages in the same order they 
	 * are inserted in the map.
	 */
	Map<String, String> map_of_msgs_for_buttons = new LinkedHashMap<String, String>() {
		{
			put("bt45", "stul");
			put("bt59", "ovoshii");
			put("bt60", "zoltii");
			put("bt61", "svet");
			put("bt47", "krasnii");
			put("bt58", "sinii");
			put("bt24", "palka");		
		}
	};
	int msgs_for_buttons_time = 5000;	// 5 seconds


	
	

	/****************************************************************************
	 * 
	 * Variables for the Exercise Phase ========================================>
	 * 
	 ***************************************************************************/



	/*
	 * Minimal Messages.
	 * The next map associates the minimal version of the messages 
	 * that the system should give with the buttons that the subject 
	 * must press. The system will verbalize the messages in the 
	 * same order they are inserted in the map.
	 */
	Map<String, String> map_of_min_msgs_for_buttons = new LinkedHashMap<String, String>() {
		{
			put("bt25", "ovoshii palka");	// 1:  flower6
			put("bt4", 	"krasnii palka"); 	// 2:  flower7
			put("bt2", 	"zoltii stul"); 	// 3:  chair8
			put("bt3", 	"ovoshii stul"); 	// 4:  chair9 
			put("bt29", "krasnii stul"); 	// 5:  chair4 
			put("bt31", "zoltii svet"); 	// 6:  lamp4
			put("bt27", "sinii stul"); 		// 7:  chair6
			put("bt44", "krasnii svet"); 	// 8:  lamp3
			put("bt9", 	"zoltii palka"); 	// 9: flower8
			put("bt15", "sinii palka"); 	// 10: flower9
			put("bt16", "sinii svet"); 		// 11: lamp2
			put("bt21", "ovoshii svet"); 	// 12: lamp5

		}
	};
	int min_msgs_for_buttons_time = 5000;	// 5 seconds


	/*
	 * Overspecified Messages.
	 * The next map associates the overspecified version of the 
	 * messages that the system should give with the buttons 
	 * that the subject must press.
	 * The system will verbalize the messages in the same order
	 * they are inserted in the map.
	 */
	Map<String, String> map_of_over_msgs_for_buttons = new LinkedHashMap<String, String>() {
		{
			put("bt25", "ovoshii palka sprava ot sinii stul");	// 1:  flower6
			put("bt4", 	"krasnii palka sleva ot sinii svet"); 	// 2:  flower7
			put("bt2", 	"zoltii stul sleva ot krasnii svet"); 	// 3:  chair8
			put("bt3", 	"ovoshii stul sleva ot ovoshii svet"); 	// 4:  chair9
			put("bt29",	"krasnii stul sprava ot zoltii palka"); // 5:  chair4
			put("bt31",	"zoltii svet sprava ot sinii palka"); 	// 6:  lamp4
			put("bt27",	"sinii stul sprava ot krasnii svet"); 	// 8:  chair6
			put("bt44", "krasnii svet sleva ot sinii stul"); 	// 9:  lamp3
			put("bt9",	"zoltii palka sprava ot zoltii svet"); 	// 10: flower8
			put("bt15",	"sinii palka sleva ot zoltii svet"); 	// 11: flower9
			put("bt16",	"sinii svet sleva ot ovoshii stul"); 	// 12: lamp2
			put("bt21",	"ovoshii svet sprava ot ovoshii stul"); // 13: lamp5
		}
	};
	int over_msgs_for_buttons_time = 7000;	// 7 seconds
	
	


	/***********************************************************/
	/**						 								  **/
	/**					System's Functions	  				  **/
	/**						 								  **/
	/***********************************************************/


	
	/**
	 * The class constructor. This is executed only once when the program
	 * you run the program.
	 * You MUST have a constructor like this, otherwise the NLG system won't
	 * be loaded by the NLG Server.
	 */
    public SashaNlgSystem(NlgServerInstance inst, World worldRef, Discretizer discretizer)
    {
        super(inst, worldRef, discretizer);
        
        // This object allow us to send our own messages to the client
        server_instance = inst;

        // We must save the GIVE's world reference so 
        // as we can get information about it.
        world = worldRef;
        
        // Sender thread creation
        sender = new DelayedSendingThread(this);
    }

    
    
    /**
     * Executed each time a new game starts.
     * The preQuestionnaire holds all the data that the subject has
     * filled in the initial questionnaire.
     */
    @Override
    public void connectionEstablished(QuestionnaireData preQuestionnaire, ClientMetadata clientMetadata)
    {
        // This two variables MUST be set like that initially.
        current_subject_room = "";
        waiting_for_new_room = true;

        // Initiate the sender object
        sender.start();

        // Log if we are gonna use minimal or overspecified references.
        /*
        if (use_overspecified)
        	log("[ORRE] : Using OVERSPECIFIED references initially for Training and Exercise rooms");
        else
        	log("[ORRE] : Using MINIMAL references for Training and Exercise rooms");
        */

    	// It's better if we avoid adding more code here.
    }



    /**
     * This is executed just before the subject ends a game. 
     */
    @Override
    public void connectionDisconnected()
    {
        // Finish the sender object
    	sender.shutdown();
    }



    /**
     * One of the two most important methods/functions.
     * It is executed whenever the subject presses a button. 
	 */
    @Override
    public void handleAction(Atom actionInstance, List<Formula> updates)
    {
    	// Gets the name of the button that was pressed.
    	String button = getButtonName(actionInstance);
    	System.out.println("The Subject has pressed: " + button);


    	// If the current room is considered by the system, i.e. is defined
    	// in map_of_room_types and we are not waiting for the subject to get in a new room.
    	if ( map_of_room_types.containsKey(current_subject_room) && !waiting_for_new_room )
    	{
    		
    		// Gets the type of the current room.
    		RoomType current_room_type = map_of_room_types.get(current_subject_room);
    		
    		boolean is_primming_room = current_room_type.equals(RoomType.PRIMMING_ROOM);  
    		boolean is_training_room = current_room_type.equals(RoomType.TRAINING_ROOM);
    		boolean is_exercise_room = current_room_type.equals(RoomType.EXERCISE_ROOM);



        	/* ******************************************************************** */
    		/* If the current room is part of the primming phase we have to			*/
        	/* send the message that is related with the button that was pressed.	*/
        	/* This is only done if such button is defined in 						*/
        	/* map_of_buttons_for_msgs and it was pressed for first time.			*/
        	/* ******************************************************************** */


        	if (is_primming_room && map_of_buttons_for_msgs.containsKey(button))
        	{
        		// If it is the first time.
        		if (!pressed_buttons.contains(button)) {
        			// Show the message for the button.
        			sender.clear();
        			sender.enqueue(map_of_buttons_for_msgs.get(button), buttons_for_msgs_time);
        		}
        		else
        			enqueueLongBySeparator(pressed_twice_msg, pressed_twice_msg_time);
        	}


        	// Add the button that was pressed to our set of pressed buttons.
        	pressed_buttons.add(button);



        	/* ******************************************************************** */
        	/* If the current room is part of the training or exercise phase, then	*/
        	/* we first have to check if the pressed button was the correct and 	*/
        	/* then give the next reference.										*/
			/* ******************************************************************** */


        	if ((is_training_room || is_exercise_room) && !mustSendReference)
        	{
        		boolean was_referred_bef = referred_buttons.contains(current_button);
        		referred_buttons.add(current_button);

        		if (!was_referred_bef)
        		{
	        		// Stop and restart the count-down
	        		do_countdown = false;
	        		timeout_frames = 0;
	
	
	        		if (button.equals(current_button))
	        		{
	        			String correct_msg = "[ORRE] : [CORRECT MANIPULATE] : Pressed:" + button;
	
	        			// Sends a positive feedback message if the room belongs to an exercise phase.
			    		if (is_training_room) {
			    			System.out.println("Training Phase = " + correct_msg);
			    			log("Training Phase = " + correct_msg); 		// Logs the string on the database.	
			    		}
			    		else {
			    			System.out.println("Exercise Phase = " + correct_msg);
			    			log("Exercise Phase = " + correct_msg); 		// Logs the string on the database.
			    			sender.clear(); 					// Send the next message immediately.
			    			enqueueLongBySeparator(positive_feedback, positive_feedback_time);
			    		}
			    		System.out.println("Correct buttons " + correct_press++);
			    		log("Number of correct buttons pressed:" + correct_press);	//Logs number of correct buttons 
	        		}
		        	else 
		        	{
		        		String incorrect_msg = "[ORRE] : [INCORRECT MANIPULATE] : Pressed: " + button + ", When needed: " + current_button; 
	
		        		// Sends a negative feedback if the room belongs to an exercise phase.
		        		if (is_training_room) {
		        			System.out.println("Training Phase = " + incorrect_msg);
		        			log("Training Phase = " + incorrect_msg); 		// Logs the string on the database.
		        		}
		        		else {
		        			System.out.println("Exercise Phase = " + incorrect_msg);
		        			log("Exercise Phase = " + incorrect_msg); 		// Logs the string on the database.
		        			sender.clear();
		        			enqueueLongBySeparator(negative_feedback, negative_feedback_time);
		        		}
	        		}
	
	        		// Sends the next referring expression only if we are not using the 
	        		// "go to the middle" condition and we are on an exercise room.
	        		if (is_training_room || (is_exercise_room && !use_go_to_middle_condition))
	        			mustSendReference = true;
	        		
	        		disable_distance_condition = false;
	        		disable_use_to_go_middle_condition = false;
        		}

    		}


        	// Prints some debug information.
        	// Obtain all the buttons that the subject has to press for this room.
        	List<String> buttons_to_press = map_of_buttons.get(current_subject_room);

        	System.out.println("Remaining buttons for the room: " + current_subject_room + " are: " 
        		+ Sets.difference(new HashSet<String>(buttons_to_press),
        				((is_primming_room) ? pressed_buttons : referred_buttons)).toString());
        	System.out.println("List of pressed buttons " + pressed_buttons.toString());
        	System.out.println("List of referred buttons " + referred_buttons.toString());

    	}



    }



    /**
     * One of the two most important functions. This is executed every 200 milliseconds
     * and gives us information about the current position and orientation of the
     * subject, and also the list of visible objects.
     * I'm going to use this for checking in which room the subject is and for close
     * the entry door when the subject gets inside a new room.
     */
    @Override
    public void handleStatusInformation(Position position, Orientation orientation, List<String> visibleObjs) throws NlgServerException
    {
    	//System.out.println("Do countdown: " + do_countdown + " is speaking: " + sender.isSpeaking());

    	// Saves the new visibleObjects list.
    	visibleObjects = visibleObjs;

    	// Will be true if the subject moved to other room.
    	boolean moved_to_other_room = false;
    	
    	// Will be true if the subject has finished with the current room.
    	boolean have_finished_with_room = false;

    	// Has the subject moved to other room and is a little far away
    	// from the door position?
    	String old_room = current_subject_room;
    	String new_room = getCurrentRoom(position).getName();
    	if ( !old_room.equals(new_room) && isSubjectAwayFromTheDoor(old_room) )
    	{
    		current_subject_room = new_room;
    		moved_to_other_room = true;

    		System.out.println("The Subject is in: " + current_subject_room);
        	System.out.println("Remaining rooms for end the experiment: " + list_of_rooms_order);

        	// Erase previous set of pressed buttons and referred buttons.
        	pressed_buttons.clear();
        	referred_buttons.clear();
    	}


    	// If the subject moves to the next room then we will close the doors from the previous room.
    	if (waiting_for_new_room && moved_to_other_room)
    	{
    		// We have to close all the doors that were controlled by the previous room.
    		if (map_of_doors.containsKey(old_room)) {
    			List<String> doors = map_of_doors.get(old_room);
    			for (String d : doors)
    				closeDoor(d);
    		}
	
    		// Sends the welcome messages for this new room if the message is 
    		// defined in the map and if the subject has to get in the room.
    		if (map_of_welcome_msgs.containsKey(current_subject_room) && list_of_rooms_order.contains(current_subject_room)) {
    			sender.clear();
    			enqueueLongBySeparator(map_of_welcome_msgs.get(current_subject_room), welcome_msgs_time);
			System.out.println(map_of_welcome_msgs.get(current_subject_room));
    		}

    		waiting_for_new_room = false;
    	}


    	if (map_of_room_types.containsKey(current_subject_room) && !waiting_for_new_room)
    	{
    		
    		// Gets the type of the current room.
    		RoomType current_room_type = map_of_room_types.get(current_subject_room);
    		
    		boolean is_tutorial_room = current_room_type.equals(RoomType.TUTORIAL_ROOM);
    		boolean is_primming_room = current_room_type.equals(RoomType.PRIMMING_ROOM);
    		boolean is_training_room = current_room_type.equals(RoomType.TRAINING_ROOM);
    		boolean is_exercise_room = current_room_type.equals(RoomType.EXERCISE_ROOM);

    		// This is a workaround to maintain the system and the messages
    		// sync with the count-down.
    		if ( mustSendReference && !sender.isSpeaking() )
    		{
    			mustSendReference = false;
    			if (current_button != null)
    				sendNextReference(current_room_type);
    			else 
    				sendFirstReference(current_room_type);
    			if (current_button != null)
    				closest_distance = position.distance(world.getPosition(current_button));
    		}

    		// If the subject moved to a new room
    		if (moved_to_other_room)
    		{

	        	/* ******************************************************************** */
	        	/* 				Training and Exercise phase specific logic				*/
	        	/* ******************************************************************** */
	
	    		// If the subject moves to a room that belongs to the TRAINING or EXERCISE phases
	        	// then we have to send the first object reference.
	    		if (is_training_room || (is_exercise_room && !use_go_to_middle_condition))
	    			mustSendReference = true;
    		}



    		// If the player is in a training or exercise room, and the system has given
	    	// a reference for an object, we have to start the count-down messages.
	    	if ((is_training_room || is_exercise_room) && do_countdown && current_button != null)
	    	{
	    		int timeout = (is_training_room) ? timeout_for_training : timeout_for_exercising;
	    		if (timeout_frames >= timeout * 5)
	    		{
	    			System.out.println("[TIME OUT] : When needed : " +  current_button);
	    			log("[ORRE] : [TIME OUT] : When needed : " +  current_button);
	    			do_countdown = false;
	    			timeout_frames = -1;
	    			referred_buttons.add(current_button);
	    			disable_distance_condition = false;
	    			disable_use_to_go_middle_condition = false;
	    			if (is_training_room || (is_exercise_room && !use_go_to_middle_condition))
	    				sendNextReference(current_room_type);
	    		}
	    		else if (timeout_frames % 5 == 0) {
	    			// Send the count down message.
	    			enqueueCountDown(current_reference_message, timeout-(timeout_frames/5), 1000);
	    			System.out.println("Countdown: " + current_reference_message + " " + (timeout-(timeout_frames/5)));
	    		}
	    		timeout_frames++;

	    		// We also have to check if we should send the over specified message
	    		// when the subject could not localize the object.
	    		if (is_exercise_room)
	    		{
	    			if (use_distance_condition && !disable_distance_condition)
	    			{
	    				current_distance = position.distance(world.getPosition(current_button));
	    				closest_distance = Math.min(closest_distance, current_distance);
	    				//System.out.println("current_dist=" + current_distance + ", old_dist=" + old_distance);
	    				//System.out.println("diff_dist=" + (old_distance - current_distance + distance_margin));
	    				
	    				if ((distance_margin>0. && current_distance - closest_distance > distance_margin)
	    					|| (distance_margin<.0 && current_distance - closest_distance > 0.)) {
	    					String msg = "[ORRE]:Triggered:BASIC_DISTANCE_CONDITION" + ((distance_margin>0.) ? "_WITH_MARGIN" : "") 
	    							+ ":" + current_button;
	    					System.out.println("current=" + current_distance + "closest=" + closest_distance + " margin=" + distance_margin);
	    					System.out.println(msg);
	    					log(msg);
	    					current_reference_message = map_of_over_msgs_for_buttons.get(current_button);
	    					logOverReferenceAttempt(current_reference_message, current_button);
	    					disable_distance_condition = true;
	    				}
	    				/*
	    				if (timeout_frames % distance_timerate == 0)
	    					old_distance = current_distance;
	    				*/
	    			}

	    			if (use_too_close_dist_condition)
	    			{
	    				// TODO: add code here.
	    			}
	    		}

	    	}

			if (is_exercise_room && use_go_to_middle_condition && !do_countdown && !disable_use_to_go_middle_condition)
			{
				Position center = getCenterOfRoom(getCurrentRoom(position));
				double dist = position.distance(center);
				//System.out.println("distance_from_middle=" + dist);
				if (dist < distance_from_middle) {
					String msg = "[ORRE]:Triggered:GO_TO_MIDDLE_CONDITION";
					System.out.println(msg);
					log(msg);
					disable_use_to_go_middle_condition = true;
					mustSendReference = true;
				}
				else if (!sender.isSpeaking()) {
					enqueueLongBySeparator(message_to_go_to_middle, 1000);
				}
			}


	    	/* ******************************************************************** */
        	/* Check if the subject has pressed all the buttons in the current room.*/
        	/* If this is the case, then we open the door for the next room and		*/
        	/* wait for the player to get inside.									*/
        	/* If the subject has finished the last room of the experiment then we  */
        	/* end the system														*/
        	/* ******************************************************************** */


	    	// Obtain all the buttons that the subject has to press for this room.
	    	List<String> buttons_to_press = map_of_buttons.get(current_subject_room);

	    	// Check if he/she has finished with the current room.
	    	have_finished_with_room = pressed_buttons.containsAll(buttons_to_press) && (is_primming_room || is_tutorial_room);
	    	have_finished_with_room = have_finished_with_room || (referred_buttons.containsAll(buttons_to_press) && (is_training_room || is_exercise_room));
    	}

    	if (!map_of_room_types.containsKey(current_subject_room)) {
    		// If the current room does not have a type, then the subject do not have to do anything on it.
    		have_finished_with_room = true;
    	}



    	// If the subject has finished with a room, we have to close
    	// the doors that are controlled by such room, send exit messages
    	// and ends the whole game if that was the last room.

    	if (have_finished_with_room && !waiting_for_new_room)
    	{
    		// The subject has finished with this room.
    		list_of_rooms_order.remove(current_subject_room);

    		System.out.println("The subject has finished with this room");
    		System.out.println("Remaining rooms for finish the experiment: " + list_of_rooms_order);

    		// If the list of rooms is empty, then the subject has finished the experiment.
    		if (list_of_rooms_order.isEmpty())
    		{
    			finishExperiment();
    		}
    		else
    		{	
	    		// We have to wait for the subject to enter inside the next room.
	    		waiting_for_new_room = true;
	    		mustSendReference = false;
	    		
	    		// Restart countdown and current_button variable.
	    		current_button = null;
	    		do_countdown = false;
	    		timeout_frames = 0;

	    		// We have to open all the doors that are controlled by the room
	    		// that was finished.
	    		if ( map_of_doors.containsKey(current_subject_room) ) {
	    			List<String> doors = map_of_doors.get(current_subject_room);
	    			for (String d : doors)
	    				openDoor(d);
	    		}

	    		// Sends to the player that he/she have to move to the next room.
	    		if (map_of_exit_msgs.containsKey(current_subject_room))
	    			enqueueLongBySeparator(map_of_exit_msgs.get(current_subject_room), exit_msgs_time);
    		}
    	}

    }

    /**
     * This is executed when the subject presses 'H' in the
     * keyboard.
     * Are we going to use this? :S
     */
    @Override
    public void handleDidNotUnderstand()
    {
        // Ignore misunderstandings
    }

    /**
     * Don't care about this.
     */
    @Override
    public void handleExternalEvent(String sourceName, Map<String, String> data)
    {
        // Ignore external events.
    }



    
    

	/***********************************************************/
	/**						 								  **/
	/**					Auxiliary Functions	  				  **/
	/**						 								  **/
	/***********************************************************/


    /**
     * Retrieves from the action the button that was manipulated.
	 * The following are the "manipulate" type of actions that the 
	 * subject perform when he/she presses a button object:
	 * 
	 * 
	 * manipulate(a,s1,s2,p)
	 * 
	 * Click on the object a, changing its state from s1 to s2, 
	 * while standing at position p.
	 * 
	 * 
	 * manipulate-stateless(a,b,s1,s2,p)
	 * 
	 * Click on the stateless object a which controls the object 
	 * b while standing at position p, changing the state of b from s1 
	 * to s2.
	 * 
	 * 
	 * manipulate-stateless-a(p)
	 * 
	 * Click on the stateless object a. This is a special case of 
	 * the "manipulate-stateless" action in which a doesn't control 
	 * another object. Notice that "a" is part of the action name 
	 * in this case, and not a parameter.

     */
    private String getButtonName(Atom action)
    {
    	String button = null;
	    String predicate = action.getPredicate();

		if ( predicate.startsWith("manipulate-stateless-") )
			button = predicate.substring( predicate.indexOf('-') + 1 ); 	// "manipulate-stateless-a(p)" case.
		else
			button = action.getArgumentAsString(0); 						// First argument.

		return button;
    }

    /**
     * Given a map of strings, this function returns the
     * value that is associated with the first key.
     * If the given map is empty then it returns null.
     */
    private String getFirstKey(LinkedHashMap<String, String> map)
    {
    	String first_key = null;
	    Iterator<String> keys = map.keySet().iterator();
		if ( keys.hasNext() )
			first_key = keys.next();
		else
			System.err.println("The given map is empty");
		return first_key;
    }

    /**
     * Given a map of strings, this function returns the
     * value that is associated with the next key than the
     * given key in the map.
     * If there is not such next value then it returns an
     * empty string.
     */
    private String getNextKey(String key, LinkedHashMap<String, String> map)
    {
    	String next_key = "";
	    Iterator<String> keys = map.keySet().iterator();
	
		while (keys.hasNext())
		{
			String k = keys.next();
			if (k.equals(key) && keys.hasNext())
				next_key = keys.next();
		}
		
		return next_key;
    }

    /**
     * Opens the given door.
     * @param door_name: The name of the door that will be 
     * opened as it figures in ORRE-0.4.giveworld file.
     */
    private void openDoor(String door_name)
    {
    	// Creates the two atoms which represents the
    	// two states for a door object.
    	Constant const_door = new Constant(door_name);
    	Constant const_open = new Constant("open");
    	Constant const_close = new Constant("closed");
    	Atom open_door = new Atom("state", const_door, const_open);
    	Atom close_door = new Atom("state", const_door, const_close);

    	// Updates world's logic representation.
    	if (world.isTrue(close_door))
    	{
    		System.out.println("Opening door: " + door_name);
    		world.deleteAtom(close_door);
    		world.addAtom(open_door);

    		// Sends a message to the client for opening the door
    		InternalMessage msg = new InternalMessage(InternalMessageType.SERVER_OPEN_DOOR, door_name);
    		server_instance.send(msg);
    	}
    	else if (!world.isTrue(close_door))
    		System.out.println("Invalid Door State for door: " + door_name);
    	
    }

    /**
     * Closes the given door.
     * @param door_name: The name of the door that will be 
     * closed as it figures in ORRE-0.4.giveworld file.
     */
    private void closeDoor(String door_name)
    {
    	// Creates the two atoms which represents the
    	// two states for a door object.
    	Constant const_door = new Constant(door_name);
    	Constant const_open = new Constant("open");
    	Constant const_close = new Constant("closed");
    	Atom open_door = new Atom("state", const_door, const_open);
    	Atom close_door = new Atom("state", const_door, const_close);

    	// Updates world's logic representation.
    	if (world.isTrue(open_door))
    	{
    		System.out.println("Closing door: " + door_name);
    		world.deleteAtom(open_door);
    		world.addAtom(close_door);

    		// Sends a message to the client for closing the door
    		InternalMessage msg = new InternalMessage(InternalMessageType.SERVER_CLOSE_DOOR, door_name);
    		server_instance.send(msg);
    	}
    	else if (!world.isTrue(close_door))
    		System.out.println("Invalid Door State for door: " + door_name);

    }

    /**
     * Retrieves the name of the room where the subject is.
     * @param position: The 3D position where the subject is.
     */
    private Room getCurrentRoom(Position position)
    {
    	double x = position.getX();
    	double z = position.getZ();

    	Room subject_room = world.getRoomAtPosition(x, z);

    	if (subject_room == null)
    		System.err.println("Could not get the Room object for the position: " + position.toString());

    	return subject_room;
    }

    /**
     * In the GIVE client there is a funny BUG in which the subject
     * could get trapped inside a door. This method will be use for 
     * ensure that the subject is far away from all the doors that
     * will be closed.
     */
    private boolean isSubjectAwayFromTheDoor(String old_room)
    {
    	boolean result = false;
    	
    	if (map_of_doors.containsKey(old_room)) {
    		List<String> doors = map_of_doors.get(old_room);
    		for (String d : doors)
    		{
    			Position subject_pos = world.getPlayerPosition();
    			Position door_pos = world.getPosition(d);
    			Orientation door_or = world.getOrientation(d);

    			if (door_or.getX() == 1)
    				result = Math.abs(subject_pos.getX() - door_pos.getX()) > 0.20d;
    			else 
    				result = Math.abs(subject_pos.getZ() - door_pos.getZ()) > 0.20d;
    		}
    	}
    	else
    		result = true;

    	return result;
    }
    
    
    /**
     * Sends a long message by splitting it into
     * one line messages.
     */
    @SuppressWarnings("unused")
	private void enqueueLong(String msg, int time)
    {
    	// Split the message by white spaces.
    	String[] words = msg.split("\\s");
    	int nchars = 0;
    	int until = 0;

    	for (int i=0; i<words.length; i++)
    	{
    		nchars += words[i].length();

    		if (nchars + (i-until) > 40)
    		{
    			if (i==0) {
    				String to_send = words[i] + ((i < words.length) ? "" : " ");
    				sender.enqueue(to_send, time);
    			}

    			String to_send = "";
				for (int j=until; j<i; j++) {
    				to_send += words[j];
    				if (j < words.length) {
    					to_send += " ";
    				}
    			}
				if (!to_send.isEmpty())
		    		sender.enqueue(to_send, time);
				
				nchars = words[i].length();
				until = i;
    		}
    	}

    	// Sends the remaining words.
    	String to_send = "";
    	for (int i=until; i<words.length; i++) {
    		to_send += words[i];
    		if (i < words.length) {
				to_send += " ";
			}
    	}
    	if (!to_send.isEmpty())
    		sender.enqueue(to_send, time);

    }
    
    
    /**
     * Splits a long messgage by the "|" separator
     * and sends the resulting messages using the sender.
     */
    private void enqueueLongBySeparator(String msg, int time)
    {
    	String[] utterances = msg.split("\\|");

    	for (int i=0; i<utterances.length; i++)
    		sender.enqueue(utterances[i], time);
    }
    
    
    /**
     * Adds to the given message the count-down number at the end, and
     * then sends the message using the sender.
     */
    private void enqueueCountDown(String message, int number, int time)
	{
		String flushRight = ""+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160
				+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160
				+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160
				+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+(char)160+"";
		
		String countdown = "";
		if (message.length() > 40)
			countdown = "\n" + flushRight + number;
		else
			countdown = "\n"+(char)160+"\n" + flushRight + number;
		
		sender.enqueue(message+countdown, time);
	}
    

    /**
     * Sends the next reference, ie, the message associated to the next
     * button of current_button.
     */
    private void sendNextReference(RoomType current_room_type)
    {
		String next_button = "";
		String next_msg = "";
		int time = 0;
	
		if (current_room_type == RoomType.TRAINING_ROOM) {
			next_button = getNextKey(current_button, (LinkedHashMap<String, String>) map_of_msgs_for_buttons);
			if (!next_button.isEmpty())
				next_msg = map_of_msgs_for_buttons.get(next_button);
			time = msgs_for_buttons_time;
		}
		else if (use_overspecified)	{	// RoomType.EXERCISE_ROOM case
			next_button = getNextKey(current_button, (LinkedHashMap<String, String>) map_of_over_msgs_for_buttons);
			if (!next_button.isEmpty())
				next_msg = map_of_over_msgs_for_buttons.get(next_button);
			time = over_msgs_for_buttons_time;
		}
		else {
			next_button = getNextKey(current_button, (LinkedHashMap<String, String>) map_of_min_msgs_for_buttons);
			if (!next_button.isEmpty())
				next_msg = map_of_min_msgs_for_buttons.get(next_button);
			time = min_msgs_for_buttons_time;
		}

		if ( !next_button.isEmpty() && !next_msg.isEmpty() )
		{
			current_button = next_button;
			current_reference_message = next_msg;

			// Log the reference attempt.
			logReferenceAttempt(next_msg, next_button);

			// Start the count-down for the current_button associated message.
			do_countdown = true;
			
			// Reset the distances from the last object.
			current_distance = 0.;
			closest_distance = 4000.;
		}	
    }
    
    /**
     * Sends the first referring expression for the current_room.
     */
    private void sendFirstReference(RoomType current_room_type)
    {
	    String first_msg;
		String first_button;
		int time;

		// Gets the first reference message.
		if (current_room_type == RoomType.TRAINING_ROOM) {
			first_button = getFirstKey((LinkedHashMap<String, String>) map_of_msgs_for_buttons);
			first_msg = map_of_msgs_for_buttons.get(first_button);
			time = msgs_for_buttons_time;
		}
		else if (use_overspecified) {	// EXERCISE_ROOM case: Overspecified messages
			first_button = getFirstKey((LinkedHashMap<String, String>) map_of_over_msgs_for_buttons);
			first_msg = map_of_over_msgs_for_buttons.get(first_button);
			time = over_msgs_for_buttons_time;
		}
		else {							// EXERCISE_ROOM case
			first_button = getFirstKey((LinkedHashMap<String, String>) map_of_min_msgs_for_buttons);
			first_msg = map_of_min_msgs_for_buttons.get(first_button);
			time = min_msgs_for_buttons_time;
		}

		// Sends the first reference for this room.
		current_button = first_button;
		current_reference_message = first_msg;

		// Log the first reference attempt.
		logReferenceAttempt(first_msg, first_button);

		// Start the count-down for the current_button associated message.
		do_countdown = true;

    }

	/**
	 * Sends the exit message and finishes the game.
	 */
	private void finishExperiment() throws NlgServerFinishedException 
	{
		enqueueLongBySeparator(end_msg+" Tu puntuaje es:  " + SashaNlgSystem.correct_press + "/26",  end_msg_time);

		// Make wait the system until the last message is shown
		// and then end the game.
		try {
			while (sender.isSpeaking())
				Thread.sleep(1000);
		} catch (InterruptedException e){}
		throw new NlgServerFinishedException("End of the experiment");	// This will end the game.
	}
	
	/**
	 * Logs a reference attemp.
	 */
	private void logReferenceAttempt(String reference_msg, String button_name)
	{
		String msg = "[ORRE] : Reference attempt = " + reference_msg;
		msg += ", for button: " + button_name;
		msg += ", player position: " + world.getPlayerPosition().toString();
		msg += ", visible objects " + visibleObjects.toString(); 
	
		System.out.println(msg);
	
		// Logs the reference attempt in the for recovering the utterance 
		// time, the player position at the time of the utterance and the visible objects
		log(msg);
	}
	
	/**
	 * Logs a reference attemp.
	 */
	private void logOverReferenceAttempt(String reference_msg, String button_name)
	{
		String msg = "[ORRE] : [OVERSPEC]: Reference attempt = " + reference_msg;
		msg += ", for button: " + button_name;
		msg += ", player position: " + world.getPlayerPosition().toString();
		msg += ", visible objects " + visibleObjects.toString(); 
	
		System.out.println(msg);
	
		// Logs the reference attempt in the for recovering the utterance 
		// time, the player position at the time of the utterance and the visible objects
		log(msg);
	}


	/**
	 * Computes the center of a given region by using a RegionDiscretizer and
     * getting a bounding box from it, then computing the center of that box
     */
    public static Position getCenterOfRoom(Room room)
    {
    	Shape shape = room.getShape();
        Rectangle2D box = shape.getBounds2D();
        Position center = new Position(box.getCenterX(), 1., box.getCenterY());
        return center;
    }


} // End of ShashaNlgServer class
